﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Messaging;
using System.Reflection;

using MessageCallAspect.Inspector.Request;
using MessageCallAspect.Inspector.Response;
using System.Diagnostics;
using System.Runtime.Remoting;

namespace MessageCallAspect.Inspector.MessageSink
{
    public abstract class SimpleMessageInspector : IMessageSink
    {
        #region fields

        private readonly IMessageSink m_NextSink;
        private readonly IModuleContext m_Context;
        private readonly object m_Target;

        #endregion

        #region ctor

        public SimpleMessageInspector(IModuleContext context, object target, IMessageSink nextSink)
        {
            m_Context = context;
            m_NextSink = nextSink;
            m_Target = target;
        }

        #endregion

        #region IMessageSink Members

        public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
        {
            throw new NotImplementedException();
        }

        public virtual IMessageSink NextSink
        {
            get
            {
                return m_NextSink;
            }
        }

        public IMessage SyncProcessMessage(IMessage msg)
        {
            Debug.WriteLine("Step into: "+this.GetType().Name);
            IMethodCallMessage methodMessage = new MethodCallMessageWrapper((IMethodCallMessage)msg);
            Stopwatch watch = new Stopwatch();
            string stopwatchKey = GenerateKeyPerProcessMessageCall(msg);
            methodMessage.Properties[stopwatchKey] = watch;
            watch.Start();

            MessageRequest messageRequest = new MessageRequest
            {
                Context = m_Context,
                Target = m_Target,
                Method = (MethodInfo)methodMessage.MethodBase,
                Arguments = methodMessage.Args
            };
            OnBeforeInvoke(messageRequest);
            IMessage returnMethod = null;
            try
            {
                returnMethod = NextSink.SyncProcessMessage(msg);
                Exception ex = ((ReturnMessage)returnMethod).Exception;
                if (ex != null)
                {
                    returnMethod = ExceptionHandling(returnMethod, ex);
                }
            }
            catch (Exception ex)
            {
                returnMethod = ExceptionHandling(msg, ex);
            }
            finally
            {
                Debug.WriteLine("Step into: " + this.GetType().Name);
                stopwatchKey = GenerateKeyPerProcessMessageCall(msg);
                watch = (Stopwatch)methodMessage.Properties[stopwatchKey];
                watch.Stop();

                IMethodReturnMessage returnMessage = (IMethodReturnMessage)returnMethod;
                MessageResponse response = new MessageResponse(messageRequest);
                response.ReturnValue = returnMessage.ReturnValue;
                response.Duration = watch.Elapsed;
                response.Exception = returnMessage.Exception;
                response.OutArgs = response.Exception != null ? new object[] { } : returnMessage.OutArgs;
                OnAfterInvoke(messageRequest, response);
            }
            return returnMethod;
        }

        #endregion

        protected virtual void OnBeforeInvoke(IMessageRequest request)
        {

        }

        protected virtual void OnAfterInvoke(IMessageRequest request, IMessageResponse response)
        {

        }

        protected virtual Exception OnException(IMessageRequest request, Exception ex)
        {
            return ex;
        }

        #region private methods

        private string GenerateKeyPerProcessMessageCall(IMessage ms)
        {
            return this.GetHashCode().ToString();
        }

        private IMessage ExceptionHandling(IMessage msg, Exception ex)
        {
            IMethodMessage methodMessage = (IMethodMessage)msg;
            MessageRequest messageRequest = new MessageRequest
            {
                Context = m_Context,
                Target = m_Target,
                Method = (MethodInfo)methodMessage.MethodBase,
                Arguments = methodMessage.Args
            };

            IMessage returnMethod = msg;
            Exception newException = OnException(messageRequest, ex);
            if (!object.ReferenceEquals(ex, newException))
            {
                returnMethod = ex.Wrap(msg);
            }

            return returnMethod;
        }

        #endregion
    }
}
